#include "shared/amf/amffile.h"
#include "shared/amf/amfobject.h"
#include "shared/amf/amfvertex.h"
#include "shared/amf/amftriangle.h"
#include "shared/amf/amfregion.h"

#include "stlfile.h"
#include <stdio.h>
#include <iostream>
#include <QFile>
#include <QDataStream>
#include <QTextStream>


/**
 * Detects an STL file as either binary or ASCII and reads it in.
 * Returns whether the file was successfully loaded.
 */
bool STLFile::read(QString path)
{
    bool isFileASCII;


    QFile inFile(path);
    if ( !inFile.open(QFile::ReadOnly) )
    {
        std::cout << "Error opening input STL file" << std::endl;
        return false;
    }

    QDataStream stream(&inFile);
    QString lastline;
    if ( stream.atEnd() ) // The file was empty, or an error occured
    {
        std::cout << "There was an error reading the specified STL file.";
        return false;
    }

    /*
     * Detect if a file is binary or ascii.
     * This can be done by reading the last line of the file, and looking for
     * the string "endsolid", indicating an ascii file.
     */
    char buffer[20]; // Will store the last 20 bytes of the file
    stream.skipRawData(inFile.size() - 19); // We only need the last 20 bytes
    stream.readRawData(buffer, 20); // Read these bytes in
    buffer[19] = '\0'; // Terminate the buffer, so we can use it to construct a string
    lastline = buffer; // Now store the char array in a nice QT variable

    inFile.close();
    isFileASCII = lastline.contains("endsolid", Qt::CaseInsensitive);
    if ( isFileASCII )
    {
        return readASCII(path);
    } else {
        return readBinary(path);
    }

}

/**
 * Reads in an STL file with a specified path, validates it, and
 * stores its data as a reference to a list of facet pointers.
 */
bool STLFile::readASCII(QString path)
{
    std::cout << "Reading ASCII STL file" << std::endl;
    QFile stl(path);
    if( !stl.open(QIODevice::ReadOnly) )
    {
        std::cout << "Failed opening the ASCII STL file for reading" << std::endl;
        return false;
    }

    QTextStream stream(&stl);
    if ( stream.atEnd() )
    {
        std::cout << "An error occured when reading the STL file";
        return false;
    }

    QString currentLine;
    STLFacet curFacet;
    int numFloats;
    int numFacets = 0;

    // The first text in the file should be "solid"
    currentLine = stream.readLine();
    if ( !currentLine.contains("solid", Qt::CaseInsensitive))
    {
        std::cout << "The STL file did not begin with the right identifier (solid)" << std::endl;
        return false;
    }

    while ( !stream.atEnd() )   // Continue reading as long as we're not at the line end
    {
        // Read the first line of the file
        currentLine = stream.readLine().trimmed();
        // Check if we're at the end of the file. If so, close the file.
        if ( currentLine.contains("endsolid", Qt::CaseInsensitive) )
        {
            std::cout << "End of file, number of facets=" << numFacets << std::endl;
            break;
        }
        // If we get to here, we're probably at "facet normal";
        numFloats = sscanf( currentLine.toStdString().c_str(), "facet normal %lf %lf %lf", &curFacet.normal.x, &curFacet.normal.y, &curFacet.normal.z );
        if ( numFloats != 3 || stream.atEnd() )
        { // The file did not deliver enough floats for the normal, or we're at an unexpected end. Break off.
            std::cout << "An error occured while reading the STL. Please check your file" << std::endl;
            return false;
        }

        // Next in line is "outer loop". No information is given in this clause, but it should be there.
        currentLine = stream.readLine().trimmed();
        if ( !currentLine.contains("outer loop", Qt::CaseInsensitive) || stream.atEnd() )
        {// the line did not match outer loop, or we're at the end of the file.
            std::cout << "An error occured while reading the STL. Please check your file" << std::endl;
            return false;
        }

        // Next in line is 'vertex', needed to set the triangle.
        currentLine = stream.readLine().trimmed();
        numFloats = sscanf(currentLine.toStdString().c_str(), "vertex %lf %lf %lf", &curFacet.triangle.v[0].x, &curFacet.triangle.v[0].y, &curFacet.triangle.v[0].z);
        if ( numFloats != 3 || stream.atEnd() )
        {
            std::cout << "An error occured while reading the STL. Please check your file" << std::endl;
            return false;
        }

        // Another vertex for the triangle
        currentLine = stream.readLine().trimmed();
        numFloats = sscanf(currentLine.toStdString().c_str(), "vertex %lf %lf %lf", &curFacet.triangle.v[1].x, &curFacet.triangle.v[1].y, &curFacet.triangle.v[1].z);
        if ( numFloats != 3 || stream.atEnd() )
        {
            std::cout << "An error occured while reading the STL. Please check your file" << std::endl;
            return false;
        }

        // And another vertex
        currentLine = stream.readLine().trimmed();
        numFloats = sscanf(currentLine.toStdString().c_str(), "vertex %lf %lf %lf", &curFacet.triangle.v[2].x, &curFacet.triangle.v[2].y, &curFacet.triangle.v[2].z);
        if ( numFloats != 3 || stream.atEnd() )
        {
            std::cout << "An error occured while reading the STL. Please check your file" << std::endl;
            return false;
        }

        // Now we should have an "endloop"
        currentLine = stream.readLine().trimmed();
        if( 0 != currentLine.compare("endloop",Qt::CaseInsensitive) || stream.atEnd() )
        {
            std::cout << "An error occured while reading the STL. Please check your file" << std::endl;
            return false;
        }

        // And lastly, an "endfacet"
        currentLine = stream.readLine().trimmed();
        if ( 0 != currentLine.compare("endfacet", Qt::CaseInsensitive) || stream.atEnd() )
        {
            return false;
        }

        // Now, if we got here, we've read a complete STL facet, add it to the mesh
        mesh_.addFacet(curFacet);
        numFacets++;
    }

    // We're at the end of the file, it's safe to close it now
    stl.close();
    // Since the mesh has been read succesfully, return true
    return true;
}

/**
 * Reads in an binary STL file with a specified path, validates it, and
 * stores its data as a reference to a list of facet pointers.
 */
bool STLFile::readBinary(QString path)
{
    /*
     * Binary STL structure:
     * 80 bytes - ASCII header
     * 4  bytes - # facets
     * ------------------------------ facet times
     * 4  bytes - float(normal X)
     * 4  bytes - float(normal Y)
     * 4  bytes - float(normal Z)
     *
     * 4  bytes - float(vertex1 X)
     * 4  bytes - float(vertex1 Y)
     * 4  bytes - float(vertex1 Z)

     * 4  bytes - float(vertex2 x)
     * 4  bytes - float(vertex2 Y)
     * 4  bytes - float(vertex2 Z)

     * 4  bytes - float(vertex3 X)
     * 4  bytes - float(vertex3 Y)
     * 4  bytes - float(vertex3 Z)

     * 2  bytes - Zero padding
     * ------------------------------
     */
    QFile stl(path);

    std::cout << "Reading binary STL file" << std::endl;
    stl.open(QFile::ReadOnly);
    QDataStream stream(&stl);

    int count; // Holds the number of bytes effectively read. If not 4, the structure of the file is not OK.
    int totalcount; // Holds the total number of triangles.
    char header[80]; // Holds the ASCII header.
    STLFacet currentFacet; // Holds the current facet.

    bool ok = stream.readRawData( (char*) &header, 80 );
    ok = stream.readRawData((char*) &totalcount, 4);

    float currentValue;

    while( !stream.atEnd() )
    {
        // First part -- reading in the normal
        count = stream.readRawData( (char*) &currentValue, 4);
        if ( count != 4 || stream.atEnd() )
        {
            return false;
        }
        currentFacet.normal.x = currentValue;

        count = stream.readRawData( (char*) &currentValue, 4);
        if ( count != 4 || stream.atEnd() )
        {
            return false;
        }
        currentFacet.normal.y = currentValue;

        count = stream.readRawData( (char*) &currentValue, 4);
        if ( count != 4 || stream.atEnd() )
        {
            return false;
        }
        currentFacet.normal.z = currentValue;

        // Second part: Reading in the 3 vertices
        for ( int i=0; i < 3; i++ )
        {
            count = stream.readRawData( (char*) &currentValue, 4);
            if ( count != 4 || stream.atEnd() )
            {
                return false;
            }
            currentFacet.triangle.v[i].x = currentValue;

            count = stream.readRawData( (char*) &currentValue, 4);
            if ( count != 4 || stream.atEnd() )
            {
                return false;
            }
            currentFacet.triangle.v[i].y = currentValue;

            count = stream.readRawData( (char*) &currentValue, 4);
            if ( count != 4 || stream.atEnd() )
            {
                return false;
            }
            currentFacet.triangle.v[i].z = currentValue;
        }

        // Last part: The 2 bytes zero padding. Read them in.
        short zeropadding;
        count = stream.readRawData( (char*) &zeropadding, 2);
        if ( count != 2 || stream.atEnd() || zeropadding != 0)
        {
            std::cout << "Zero padding not okay in STL file" << std::endl;
            return false;
        }

        mesh_.addFacet(currentFacet);
    }

    // If we've gotten to this place, then we've come to the end of the file, and can close it.
    stl.close();

    return true;
}


/**
 * Gets the mesh of this STL file
 */
const STLMesh& STLFile::GetMesh()
{
    return mesh_;
}

void STLFile::convertToAMF(AMFFile* output) {

  // remove all contents of the output file1
  output->clear();

  QList<AMFVertex*> welded_vertices;
  QList<AMFTriangle*> welded_triangles;

  const QList<STLFacet*>& facets = mesh_.GetFacets();

  for (int i = 0; i < facets.length(); i++) {
    for (int j = 0; j < 3; j++) {
      welded_vertices.push_back(new AMFVertex(facets[i]->triangle.v[j], welded_vertices.size()));
    }
    welded_triangles.push_back(new AMFTriangle(welded_vertices.size() - 3,
                                                welded_vertices.size() - 2,
                                                welded_vertices.size() - 1));
  }

  todo("kwg8","This method doesn't actually weld vertices to create a solid mesh");
  // TODO: welding

  // Welded triangles contains triangles with vertices that have all unique coördinates
  // Need to generate a list of vertices, which we can couple with the main

  // Generate the output mesh
  AMFMesh* amfMesh = new AMFMesh();
  amfMesh->Fill(welded_triangles, welded_vertices);

  // fill the output object
  AMFObject* object = new AMFObject();
  object->GiveMesh(amfMesh);
  output->addObject(object);
}
